#include "mkvmuxfilter.hpp"
#include "cmemallocator.hpp"
#include <vfwmsgs.h>
#include <uuids.h>
#include <dvdmedia.h>
#include <cassert>
#include "cstreamio.hpp"
#include "mpegparser/MPEGVideoBuffer.h"


namespace MkvMux
{


Outpin::Outpin(Filter* p) :
    Pin(p, L"outpin", PINDIR_OUTPUT),
    m_pFile(0)
{
    AM_MEDIA_TYPE mt;
    
    mt.majortype = MEDIATYPE_Stream;
    
    //TODO: we need a subtype guid here that indicates
    //that this is a "mkv stream" (see dllentry.cpp).
    mt.subtype = GUID_NULL;
    
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = GUID_NULL;
    mt.pUnk = 0;
    mt.cbFormat = 0;
    mt.pbFormat = 0;
    
    m_preferred.Add(mt);
}


Outpin::~Outpin()
{
    assert(m_pFile == 0);
    assert(!bool(m_pStream));
    assert(!bool(m_pAllocator));
}


void Outpin::Init()
{
    assert(m_pFile == 0);
    
    if (!bool(m_pStream))
        return;
        
    if (bool(m_pAllocator))
    {
        const HRESULT hr = m_pAllocator->Commit();
        assert(SUCCEEDED(hr));  //TODO
        hr;
    }
    
    const InpinVideo& iv = m_pFilter->m_inpin_video;
    
    if (iv.m_connection == 0)
        return;
        
    //TODO: must handle audio case
    
    assert(!iv.m_connection_mt.Empty());

    const AM_MEDIA_TYPE& mt = iv.m_connection_mt[0];
    assert(mt.pbFormat);  //fully vetted in InpinVideo::QueryAccept
    
    const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat);
    assert(mpg.cbSequenceHeader);
    
    const VIDEOINFOHEADER2& vih = mpg.hdr;
    const BITMAPINFOHEADER& bmih = vih.bmiHeader;
    bmih;

    const DWORD cb = mpg.cbSequenceHeader;
    assert(cb >= 4);
    
    //http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html#seq

    const BYTE* const h = MPEG2_SEQUENCE_INFO(&mpg);
    assert(h[0] == 0);
    assert(h[1] == 0);
    assert(h[2] == 1);
    assert(h[3] == 0xB3);
    
    m_pFile = new (std::nothrow) CStreamIO(m_pStream, h, cb);
    assert(m_pFile);  //TODO
}


void Outpin::Final()
{
    delete m_pFile;
    m_pFile = 0;
    
    if (bool(m_pAllocator))
    {
        const HRESULT hr = m_pAllocator->Decommit();
        assert(SUCCEEDED(hr));  //TODO
        hr;
    }
}


HRESULT Outpin::QueryInterface(const IID& iid, void** ppv)
{
    if (ppv == 0)
        return E_POINTER;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
    
    if (iid == __uuidof(IUnknown))
        pUnk = static_cast<IPin*>(this);
        
    else if (iid == __uuidof(IPin))
        pUnk = static_cast<IPin*>(this);
        
    else
    {
        pUnk = 0;
        return E_NOINTERFACE;
    }
    
    pUnk->AddRef();
    return S_OK;
}


ULONG Outpin::AddRef()
{
    return m_pFilter->AddRef();
}


ULONG Outpin::Release()
{
    return m_pFilter->Release();
}
    

HRESULT Outpin::Connect(
    IPin* pin,
    const AM_MEDIA_TYPE* pmt)
{
    if (pin == 0)
        return E_POINTER;
        
    IStreamPtr pStream;
    
    HRESULT hr = pin->QueryInterface(&pStream);
    
    if (hr != S_OK)
        return hr;

    Filter::Lock lock;
    
    hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (m_pFilter->m_state != State_Stopped)
        return VFW_E_NOT_STOPPED;
        
    if (m_connection)
        return VFW_E_ALREADY_CONNECTED;
        
    assert(!bool(m_pAllocator));
    assert(!bool(m_pStream));
    assert(m_pFile == 0);
        
    m_connection_mt.Clear();
    
    if (pmt)
    {
        hr = QueryAccept(pmt);
         
        if (hr != S_OK)
            return VFW_E_TYPE_NOT_ACCEPTED;
            
        hr = pin->ReceiveConnection(this, pmt);
        
        if (FAILED(hr))
            return hr;
            
        hr = m_connection_mt.Add(*pmt);
        
        if (FAILED(hr))
            return hr;
    }
    else
    {
        assert(m_preferred.Size() == 1);
        const AM_MEDIA_TYPE& mt = m_preferred[0];

        hr = pin->ReceiveConnection(this, &mt);
        
        if (FAILED(hr))
            return hr;
            
        hr = m_connection_mt.Add(mt);        
        
        if (FAILED(hr))
            return hr;
    }

    _COM_SMARTPTR_TYPEDEF(IMemInputPin, __uuidof(IMemInputPin));        

    const IMemInputPinPtr pMemInput(pin);
    
    if (bool(pMemInput))
    {
        hr = pMemInput->GetAllocator(&m_pAllocator);

        if (FAILED(hr))
        {
            hr = CMemAllocator::CreateInstance(&m_pAllocator);
            
            if (FAILED(hr))
                return VFW_E_NO_ALLOCATOR;
        }
        
        assert(bool(m_pAllocator));
        
        ALLOCATOR_PROPERTIES props;

        props.cBuffers = -1;    //number of buffers
        props.cbBuffer = -1;    //size of each buffer, excluding prefix
        props.cbAlign = -1;     //applies to prefix, too
        props.cbPrefix = -1;    //imediasample::getbuffer does NOT include prefix

        hr = pMemInput->GetAllocatorRequirements(&props);

        if (props.cBuffers < 0)
            props.cBuffers = 0;

        if (props.cbBuffer < 0)
            props.cbBuffer = 0;

        if (props.cbAlign <= 0)
            props.cbAlign = 1;

        if (props.cbPrefix < 0)
            props.cbPrefix = 0;

        hr = m_pAllocator->SetProperties(&props, &m_props);
        
        if (FAILED(hr))
            return hr;
            
        hr = pMemInput->NotifyAllocator(m_pAllocator, 0);  //allow writes
        
        if (FAILED(hr) && (hr != E_NOTIMPL))
            return hr;
    }
    
    m_pStream = pStream;

    //TODO: use a smart ptr for this
    m_connection = pin;
    m_connection->AddRef();
    
    return S_OK;
}


HRESULT Outpin::ReceiveConnection( 
    IPin*,
    const AM_MEDIA_TYPE*)
{
    return E_UNEXPECTED;  //for input pins only
}


HRESULT Outpin::QueryAccept(const AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return E_POINTER;
        
    const AM_MEDIA_TYPE& mt = *pmt;
    
    if (mt.majortype == MEDIATYPE_Stream)
        return S_OK;
        
    return S_FALSE;
}


HRESULT Outpin::QueryInternalConnections(IPin** pa, ULONG* pn)
{
    if (pn == 0)
        return E_POINTER;

    Filter::Lock lock;
    
    HRESULT hr = lock.Seize(m_pFilter);
    
    if (FAILED(hr))
        return hr;
    
    if (*pn == 0)
    {
        if (pa == 0)  //query for required number
        {
            *pn = 2;
            return S_OK;
        }
        
        return S_FALSE;  //means "insufficient number of array elements"
    }
    
    *pn = 0;

    if (pa == 0)
        return E_POINTER;
    
    if (*pn < 2)
        return S_FALSE;  //means "insufficient number of array elements"
    
    IPin*& vpin = pa[0];
    
    vpin = &m_pFilter->m_inpin_video;
    vpin->AddRef();
    
    IPin*& apin = pa[1];
    
    apin = &m_pFilter->m_inpin_audio;
    apin->AddRef();

    *pn = 2;
    return S_OK;        
}

        
HRESULT Outpin::EndOfStream()
{
    return E_UNEXPECTED;  //for inpins only
}


HRESULT Outpin::NewSegment(
    REFERENCE_TIME,
    REFERENCE_TIME,
    double)
{
    return E_UNEXPECTED;
}


HRESULT Outpin::BeginFlush()
{
    return E_UNEXPECTED;
}


HRESULT Outpin::EndFlush()
{
    return E_UNEXPECTED;
}


HRESULT Outpin::OnDisconnect()
{
    assert(m_pFile == 0);  //because we're stopped
    
    m_pStream = 0;
    m_pAllocator = 0;
    
    return S_OK;
}

   
HRESULT Outpin::WriteVideo(IMediaSample* p)
{
    if (m_pFile == 0)
    {
        if (p == 0) //EOS
            return S_OK;
            
        return S_FALSE;  //tells upstream pin not to send any more samples
    }
            
    return m_pFile->WriteVideo(p);
}


HRESULT Outpin::GetDuration(LONGLONG* p) const
{
    //I don't know whether "duration" is meaningful
    //for a mux graph (since we're assuming that not
    //all upstream filters are capable of returning
    //duration).  However, in general I try to return
    //some value, even if its meaningfulness is dubious,
    //intead of returning an error code.
    
    return GetCurrentPosition(p);
}
    

HRESULT Outpin::GetCurrentPosition(LONGLONG* p) const
{
    if (p == 0)
        return E_POINTER;
        
    if (!bool(m_pStream) || (m_pFile == 0))
        return VFW_E_NOT_CONNECTED;
        
    *p = m_pFile->GetDuration();
        
    return S_OK;
}


HRESULT Outpin::GetStopPosition(LONGLONG* p) const
{
    //We really should forward this to the upstream filter,
    //but we did say that getting current position is the
    //only meaningful thing we return, so if they ask us
    //for the stop time anyway, then we return whatever
    //we decide to return.
        
    return GetCurrentPosition(p);
}


HRESULT Outpin::SetPositions( 
    LONGLONG* pCurrPos,
    DWORD dwCurr_,
    LONGLONG* pStopPos,
    DWORD dwStop_) const
{
    if (!bool(m_pStream) || (m_pFile == 0))
        return VFW_E_NOT_CONNECTED;

    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    const DWORD dwStopPos = dwStop_ & AM_SEEKING_PositioningBitsMask;
    
    if (dwStopPos != AM_SEEKING_NoPositioning)
        return E_NOTIMPL;
    
    if (dwCurrPos == AM_SEEKING_NoPositioning)
    {
        if (dwCurr_ & AM_SEEKING_ReturnTime)
        {
            if (pCurrPos == 0)
                return E_POINTER;
                
            *pCurrPos = m_pFile->GetDuration();
        }
        
        if (dwStop_ & AM_SEEKING_ReturnTime)
        {
            if (pStopPos == 0)
                return E_POINTER;
                
            *pStopPos = m_pFile->GetDuration();  //see comments above
        }
        
        return S_FALSE;  //no position change
    }
    
    //Caller is trying to set curr position.
    //Let's be kind and allow him to set time to 0 when
    //we're stopped.
    
    if (pCurrPos == 0)
        return E_INVALIDARG;  
    
    LONGLONG& t = *pCurrPos;
    
    switch (dwCurrPos)
    {
        case AM_SEEKING_AbsolutePositioning:
            if (t < 0)
                return E_INVALIDARG;
                
            if (t == 0)
            {
                if (m_pFilter->m_state != State_Stopped)
                    return VFW_E_NOT_STOPPED;
                    
                break;
            }

            return E_NOTIMPL;

        case AM_SEEKING_RelativePositioning:
            if (t == 0)
            {
                if (m_pFilter->m_state != State_Stopped)
                    return VFW_E_NOT_STOPPED;
                    
                break;
            }

            return E_NOTIMPL;
                        
        case AM_SEEKING_IncrementalPositioning:
        default:
            return E_INVALIDARG;  //applies only to stop pos
    }
    
    //No need to return curr time, because it's already 0.
                        
    if (dwStop_ & AM_SEEKING_ReturnTime)
    {
        if (pStopPos == 0)
            return E_POINTER;
            
        *pStopPos = 0;
    }
            
    return S_OK;
}
    

HRESULT Outpin::GetPositions( 
   LONGLONG* pCurrPos,
   LONGLONG* pStopPos) const
{
    if (!bool(m_pStream) || (m_pFile == 0))
        return VFW_E_NOT_CONNECTED;
        
    LONGLONG t;
        
    if (m_pFilter->m_state == State_Stopped)
        t = 0;  //time we would return, if play were to resume
    else
        t = m_pFile->GetDuration();  //time of frame we have just written
        
    if (pCurrPos)
        *pCurrPos = t;
        
    if (pStopPos)
        *pStopPos = t;  //see note above

    return S_OK;
}
    

HRESULT Outpin::GetAvailable( 
    LONGLONG* pEarliest,
    LONGLONG* pLatest) const
{
    if (pEarliest)
        *pEarliest = 0;
        
    if (pLatest)
    {
        if (!bool(m_pStream) || (m_pFile == 0))
            return VFW_E_NOT_CONNECTED;
            
        LONGLONG& t = *pLatest;
            
        if (m_pFilter->m_state == State_Stopped)
            t = 0;  //time we would return, if play were to resume
        else
            t = m_pFile->GetDuration();  //time of frame we have just written
    }
        
    return S_OK;
}


} //end namespace VTSRead
